using System;
using System.Collections;

using GLib;

/*  This file is part of LibGChart.
 *
 * AUTHORS
 *       Sascha Dewald        <sascha.dewald@googlemail.com>
 *
 * Copyright (C) 2009 Sascha Dewald
 *
 *  
 *  
 *  LibGChart is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  Foobar is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with LibGChart.  If not, see <http://www.gnu.org/licenses/>.
 * 
 */

using Cairo;

namespace LibGChart
{
	/*
		Class GKraftWegDiagramm
		@Brief: Base Class for KraftWegDiagramm
		@Description: 
	*/
	[System.ComponentModel.ToolboxItem(true)]
	public partial class GChart : Gtk.Bin
	{
		private int debugCount = 0;
		
		private int offsetChartLeft;
		private int offsetChartBottom;
		private int offsetChartRight;
		private int offsetChartTop;
		
		private int areaHeight, areaWidth;
		private double chartStartX, chartStartY, chartStopX, chartStopY;
		private double chartWidth, chartHeight;
		
		private bool refreshChartActive; //TODO: this should be thread safe
		
		private GCaption title;
		private GSeriesList seriesList;
		
		private GAxis xaxis, yaxis;
		
		private Color colorBackground;
		private Color colorBackgroundBorder;
		private Color colorChart;
		private Color colorChartBorder;
		
		internal event DelegateOnChanged onAreaHeightChanged;
		internal event DelegateOnChanged onAreaWidthChanged;
		
		internal event DelegateOnChanged onOffsetLeftChanged;
		internal event DelegateOnChanged onOffsetRightChanged;
		internal event DelegateOnChanged onOffsetTopChanged;
		internal event DelegateOnChanged onOffsetBottomChanged;
		
		/**
		 * GChart Class
		 */
		public GChart()
		{
			this.Build();
			
			colorBackground 		= new Color(0.2, 0.2, 0.2);
			colorBackgroundBorder	= new Color(0.6, 0.6, 0.6);
			colorChart				= new Color(0.4, 0.4, 0.4);
			colorChartBorder		= new Color(0.8, 0.8, 0.8);
			
			// Create the title of the chart
			title = new GCaption();
			title.FontSpace = 3;

			title.OnFontSpaceChanged += calc_title_pos_upright;
			title.OnYBearingChanged += calc_title_pos_upright;
			title.OnHeightChanged += calc_title_pos_upright;
			title.OnWidthChanged += calc_title_pos_parallel;
			
			title.OnTextChanged += refresh_chart; 
			title.OnFontSizeChanged += refresh_chart;
			title.OnFontSpaceChanged += refresh_chart;
			
			// Create the XAxis
			xaxis = new GAxis(this, AxisOrientation.Bottom);
			xaxis.ValueMin = 0;
			xaxis.ValueMax = 200;
			//xaxis.onChanged += refresh_chart; // TODO: optimize
			
			// Create the YAxis
			yaxis = new GAxis(this, AxisOrientation.Left);
			yaxis.ValueMin = 0;
			yaxis.ValueMax = 20;
			//yaxis.onChanged += refresh_chart; // TODO: optimize
			
			// Allocate ArrayList with Capacity 8 for the series
			//seriesList = new ArrayList(8);
			seriesList = new GSeriesList(xaxis, yaxis);
			
			xaxis.onTicksChanged += seriesList.calc_x;
			yaxis.onTicksChanged += seriesList.calc_y;
			
			yaxis.onOffsetChanged += calc_left_offset;
			yaxis.onOffsetChanged += refresh_chart;
			
			xaxis.onOffsetChanged += calc_bottom_offset;
			xaxis.onOffsetChanged += refresh_chart;
			
			xaxis.onTicksChanged += refresh_chart;
			yaxis.onTicksChanged += refresh_chart;
			
			xaxis.onNeedRedraw += refresh_chart;
			yaxis.onNeedRedraw += refresh_chart;
		}
		
		/**
		 * Property X-Axis
		 */ 
		public GAxis axisX {
			get { return xaxis; }
		}
		
		/**
		 * Property Y-Axis
		 */
		public GAxis axisY {
			get { return yaxis; }
		}
		
		/**
		 * Property Title
		 */
		public GCaption Title {
			get { return title; }
		}
		
		/**
		 * Property OffsetChartLeft
		 */
		public int OffsetChartLeft
		{
			get { return offsetChartLeft; }
			set {
				if (offsetChartLeft != value)
				{
					offsetChartLeft = value;
					
					if (onOffsetLeftChanged != null)
						onOffsetLeftChanged();
					
					calc_left_offset();
					refresh_chart();
				}
			}
		}
		
		/**
		 * Property OffsetChartBottom
		 */
		public int OffsetChartBottom
		{
			get { return offsetChartBottom; }
			set {
				if (offsetChartBottom != value)
				{
					offsetChartBottom = value;
					
					if (onOffsetBottomChanged != null)
						onOffsetBottomChanged();
					
					calc_bottom_offset();
					refresh_chart();
				}
			}
		}
		
		/**
		 * Property OffsetChartRight
		 */
		public int OffsetChartRight
		{
			get { return offsetChartRight; }
			set {
				if (offsetChartRight != value)
				{
					offsetChartRight = value;
					
					if (onOffsetRightChanged != null)
						onOffsetRightChanged();
					
					calc_right_offset();
					refresh_chart();
				}
			}
		}
		
		/**
		 * Property OffsetChartTop
		 */
		public int OffsetChartTop
		{
			get { return offsetChartTop; }
			set {
				if (offsetChartTop != value)
				{
					offsetChartTop = value;
					
					if (onOffsetTopChanged != null)
						onOffsetTopChanged();
					
					calc_title_pos_upright();
					calc_top_offset();
					refresh_chart();
				}
			}
		}
		
		internal int AreaHeight
		{
			get { return areaHeight; }
			set {
				if (areaHeight != value)
				{
					areaHeight = value;
					
					calc_bottom_offset();
					
					if (onAreaHeightChanged != null)
						onAreaHeightChanged();
					
					refresh_chart();
				}
			}
		}
		
		internal int AreaWidth
		{
			get { return areaWidth; }
			set {
				if (areaWidth != value)
				{
					areaWidth = value;
					
					if (onAreaWidthChanged != null)
						onAreaWidthChanged();
					
					calc_right_offset();
					refresh_chart();
				}
			}
		}
		
		internal double ChartHeight
		{
			get { return chartHeight; }
			set {
				if (chartHeight != value)
				{
					chartHeight = value;
					
					// calc the coords of the series
					seriesList.calc_y();
				}
			}
		}
		
		internal double ChartWidth
		{
			get { return chartWidth; }
			set {
				if (chartWidth != value)
				{
					chartWidth = value;
					
					// calc the title position
					calc_title_pos_parallel();
					
					// calc the coords of the series
					seriesList.calc_x();
				}
			}
		}

		/**
		 * function set_color_chart
		 */
		public GSeriesList SeriesList
		{
			get { return seriesList; }
		}
		
		/**
		 * function set_color_chart
		 */
		public void set_color_chart (double r, double g, double b)
		{
			colorChart = new Color(r, g, b);
		}
		
		/**
		 * function set_color_chart_border
		 */
		public void set_color_chart_border (double r, double g, double b)
		{
			colorChartBorder = new Color(r, g, b);
		}
		
		/**
		 * function set_color_background
		 */
		public void set_color_background (double r, double g, double b)
		{
			colorBackground = new Color(r, g, b);
		}
		
		/**
		 * function set_color_background_border
		 */
		public void set_color_background_border (double r, double g, double b)
		{
			colorBackgroundBorder = new Color(r, g, b);
		}
		
		/**
		 * function refresh_chart, will be called if something change, and the chart must recalc the coords
		 */
		internal void refresh_chart()
		{
			if (!refreshChartActive) // TODO: this is just a simple hack, but it is not thread-aware
			{
				refreshChartActive = true;
				drawingareaChart.QueueDraw();
			}
		}
		
		/**
		 * function calc_title_pos, calculate the position of the title
		 */
		private void calc_title_pos_parallel()
		{
			// Calculate ChartTitle parallel Position
			title.PosParallel = (chartWidth - title.Width) / 2 + chartStartX;
		}
		
		private void calc_title_pos_upright()
		{
			// Calculate ChartTitle upright Position
			calc_top_offset();
			title.PosUpright = (-1 * title.YBearing) + title.FontSpace + offsetChartTop;
		}
		
		/**
		 * function calc_top_offset, calculate the internal top offset
		 */
		private void calc_top_offset()
		{
			chartStopY	= offsetChartTop + title.Height + 2 * title.FontSpace;
			
			xaxis.OffsetChartUprightStop = chartStopY;
			yaxis.OffsetChartParallelStop = -chartStopY;
			
			ChartHeight	= chartStartY - chartStopY;
		}
		
		/**
		 * function calc_bottom_offset, calculate the internal bottom offset
		 */
		private void calc_bottom_offset()
		{
			chartStartY	= areaHeight - (xaxis.AxisOffset + offsetChartBottom); 
			
			xaxis.OffsetChartUprightStart =  offsetChartBottom - areaHeight;
			yaxis.OffsetChartParallelStart = -chartStartY;
			
			ChartHeight	= chartStartY - chartStopY;
		}
		
		/**
		 * function calc_left_offset, calculate the internal left offset
		 */
		private void calc_left_offset()
		{
			// TODO: calc LabelWidth
			chartStartX	= yaxis.AxisOffset + offsetChartLeft;
			
			yaxis.OffsetChartUprightStart = offsetChartLeft;
			xaxis.OffsetChartParallelStart = chartStartX;
			
			ChartWidth 	= chartStopX - chartStartX;
		}
		
		/**
		 * function calc_right_offset, calculate the internal right offset
		 */
		private void calc_right_offset()
		{
			chartStopX	= areaWidth - offsetChartRight;
			
			yaxis.OffsetChartUprightStop = chartStopX;
			xaxis.OffsetChartParallelStop = chartStopX;
			
			ChartWidth 	= chartStopX - chartStartX;
		}
		
		/**
		 * function drawBackground, draws the chart background
		 */
		private void drawBackground(Cairo.Context cr)
		{
			cr.Save();
			
			// Draw Border
			cr.MoveTo(0, 0);
			cr.LineTo(0, areaHeight);
			cr.LineTo(areaWidth, areaHeight);
			cr.LineTo(areaWidth, 0);
			//cr.LineTo(0, 0);
			cr.ClosePath();
			
			cr.Color = colorBackground;
			cr.FillPreserve();
			
			cr.Color = colorBackgroundBorder;
			cr.Stroke(); 
			
			cr.Restore();
		}
		
		
		/**
		 * function drawChartBackground, draw the Background of the chart area, inside the axis.
		 */
		private void drawChartBackground(Cairo.Context cr)
		{
			cr.Save();
			
			cr.MoveTo(chartStartX, chartStartY);
			cr.LineTo(chartStartX, chartStopY);
			cr.LineTo(chartStopX, chartStopY);
			cr.LineTo(chartStopX, chartStartY);
			cr.ClosePath();
			
			cr.Color = colorChart;
			cr.FillPreserve();
			
			cr.Color = colorChartBorder;
			cr.Stroke();
			
			cr.Restore();
		}
		 
		/**
		 * function OnDrawingareaChartExposeEvent, will be called if the chart should be redrawn
		 */
		protected virtual void OnDrawingareaChartExposeEvent (object o, Gtk.ExposeEventArgs args)
		{
			//Cairo.Graphics g = Cairo.Graphics.CreateDrawable (area.GdkWindow);
			Gtk.DrawingArea area = (Gtk.DrawingArea) o;
			Cairo.Context cr = Gdk.CairoHelper.Create(area.GdkWindow);
			
			Console.WriteLine(debugCount++ + ". Chart refresh.");
			refreshChartActive = false;
			
			cr.Save();
			
			drawBackground(cr);
			drawChartBackground(cr);
			
			title.draw(cr);
   
			xaxis.draw(cr);
			yaxis.draw(cr);
			
			seriesList.draw(cr, chartStartX, chartStopY, chartWidth, chartHeight);
			
			//legend.draw(cr);
			
			cr.Restore();
			
			((IDisposable) cr.Target).Dispose();                                      
			((IDisposable) cr).Dispose();

			args.RetVal = true;
		}
	
		/**
		 * function OnDrawingareaChartSizeAllocated, will be called if the Size of the DrawingArea changed
		 */
		protected virtual void OnDrawingareaChartSizeAllocated (object o, Gtk.SizeAllocatedArgs args)
		{
			//Console.WriteLine("SizeAllocated");
			if (drawingareaChart.IsRealized)
			{
				//Console.WriteLine("SizeAllocated is Realized");
				AreaWidth	= drawingareaChart.Allocation.Width;
				AreaHeight	= drawingareaChart.Allocation.Height;
			}
		}
		
		/**
		 * function OnDrawingareaChartRealized, will be called if the DrawingArea is realized (created and space allocated, etc...)
		 */
		protected virtual void OnDrawingareaChartRealized (object sender, System.EventArgs e)
		{
			//Console.WriteLine("ChartRealized");
			// Initialize the Chart
			AreaWidth	= drawingareaChart.Allocation.Width;
			AreaHeight	= drawingareaChart.Allocation.Height;
		}
	}
	
}
